# Tian Wang 19375546
# CS 194-4 HW 2

require 'time'

class Movie
  #Modules 2.
  include Comparable
  attr_reader :name
  attr_reader :showtime
  def initialize(name,showtime)
    @name = name
    # Basics 3.
    if showtime.is_a? Time
      @showtime = showtime
    elsif showtime.is_a? String
      @showtime = Time.parse(showtime)
    else
       raise ArgumentError, "Takes in a String or Time object", caller
    end
  end
  #Basics 4.
  def name_and_showtime
    if @showtime < Time.now
      name + ": already started at " + @showtime.strftime("%I:%M%p")
    else
      name + ": next show at " + @showtime.strftime("%I:%M%p")
    end
  end
  #Modules 1.
  def <=> (movie)
    if self.showtime > movie.showtime
      1   
    elsif self.showtime < movie.showtime
      -1
    else
      0
    end
  end
end

movies = []
movies << Movie.new("Orphan", Time.parse("1:00 pm"))
movies << Movie.new("Orphan", Time.parse("4:05 pm"))
movies << Movie.new("Orphan", Time.parse("7:00 pm"))
movies << Movie.new("GI Joe", Time.parse("1:05 pm"))
movies << Movie.new("GI Joe", Time.parse("4:30 pm"))
movies << Movie.new("GI Joe", Time.parse("6:50 pm"))
movies << Movie.new("Rocky Horror", Time.parse("12:00 am"))
movies << Movie.new("District 9", Time.parse("11:00 am"))
movies << Movie.new("District 9", Time.parse("3:00 pm"))

# Basics 1. Finger Exercise
class Numeric
  def squared
    self * self
  end
end

# Basics 2. 
# The Time class responds to the parse method but the String class does not. 
# To use the latter syntax, we would need to add the parse method to the String class.

# Modules 3.
# Comparable allows the Enumerable module to be used, and implements it.

# Higher-order 1.
module Enumerable
  def group_by (&func)
      temp = Hash.new
      self.each do |obj|
        if temp[func.call(obj)] == nil
          temp[func.call(obj)] = Array.new
        end        
        temp[func.call(obj)] << obj
    end
    temp
  end
end

# Higher-order 2.
movies.group_by {|x| x.name}
=begin
irb(main):960:0> movies.group_by {|x| x.name}
=> {"District 9"=>[#<Movie:0x30289a8 @showtime=Tue Sep 15 11:00:00 -0700 2009, @
name="District 9">, #<Movie:0x3011668 @showtime=Tue Sep 15 15:00:00 -0700 2009,
@name="District 9">], "Orphan"=>[#<Movie:0x31ff8a8 @showtime=Tue Sep 15 13:00:00
 -0700 2009, @name="Orphan">, #<Movie:0x31faf4c @showtime=Tue Sep 15 16:05:00 -0
700 2009, @name="Orphan">, #<Movie:0x31f64d8 @showtime=Tue Sep 15 19:00:00 -0700
 2009, @name="Orphan">], "GI Joe"=>[#<Movie:0x31f1a3c @showtime=Tue Sep 15 13:05
:00 -0700 2009, @name="GI Joe">, #<Movie:0x31ecf78 @showtime=Tue Sep 15 16:30:00
 -0700 2009, @name="GI Joe">, #<Movie:0x31e8040 @showtime=Tue Sep 15 18:50:00 -0
700 2009, @name="GI Joe">], "Rocky Horror"=>[#<Movie:0x31e3108 @showtime=Tue Sep
 15 00:00:00 -0700 2009, @name="Rocky Horror">]}
=end

# Higher-order 3-1.
movies.select {|x| x.showtime > Time.parse("5:00pm")}
=begin
irb(main):964:0> movies.select {|x| x.showtime > Time.parse("5:00pm")}
=> [#<Movie:0x31f64d8 @showtime=Tue Sep 15 19:00:00 -0700 2009, @name="Orphan">,
 #<Movie:0x31e8040 @showtime=Tue Sep 15 18:50:00 -0700 2009, @name="GI Joe">]
=end

# Higher-order 3-2.
movies.select {|x| x.showtime > Time.parse("5:00pm")}.length
=begin
irb(main):965:0> movies.select {|x| x.showtime > Time.parse("5:00pm")}.length
=> 2
=end

# Higher-order 3-3.
movies.select {|x| x.showtime > Time.parse("5:00pm")}.map {|x| x.name}.uniq
=begin
irb(main):993:0> movies.select {|x| x.showtime > Time.parse("5:00pm")}.map {|x|
x.name}.uniq
=> ["Orphan", "GI Joe"]
=end

# Iterators 1.
module Enumerable
  def each_even
    self.each do |item|
      yield(item) if (item % 2).zero?
    end
  end
  # Iterators 2.
  def each_grouped_by_first_letter
    self.group_by { |x| x[0,1].to_s }.each do |key, value|
      yield key, value
    end
  end
end

# Extending Classes
class IndifferentHash < Hash
  def [] (key)
    if key.is_a? String
      key = key.to_sym
    end
    super key
  end
  def []= (key, value)
    if key.is_a? String
      key = key.to_sym
    end
    super key, value
  end
end

# Language Extension 1.
=begin
  irb(main):1234:0> x=""
=> ""
irb(main):1235:0> x.empty?
=> true
irb(main):1236:0> x=nil
=> nil
irb(main):1237:0> x.empty?
NoMethodError: undefined method `empty?' for nil:NilClass
        from (irb):1237
        from ♥:0
=end

# Language Extension 4.
# Add to the Object class so that all objects can respond to it.
class Object
# Language Extension 2.
  def blank?
    if self.nil?
      true
    else
      self.empty?
    end
  end
end


# Language Extension 2.
class String
  def blank?
    self.strip.empty?
  end
end
